﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using RootfusCoreComponents;
using RootfusHostView;

namespace RoofusUIControls
{
    /// <summary>
    /// Generic item class that holds an item that is being shown on the surface.
    /// </summary>
    public partial class SurfaceItem : UserControl
    {
        /// <summary>
        /// Cache the user element that we are displaying.
        /// </summary>
        UIElement _ele = null;

        /// <summary>
        /// Hold onto the item we need to display.
        /// </summary>
        RootfusSurfaceItemWrapper _item;

        /// <summary>
        /// Create a new surface item - but need to do it with the basic surface item.
        /// </summary>
        /// <param name="item"></param>
        public SurfaceItem(RootfusSurfaceItemWrapper item)
        {
            InitializeComponent();
            _item = item;

            ///
            /// Get the default display up and displayed.
            /// 

            _ele = item.SurfaceUI;
            Grid.SetColumn(_ele, 1);
            Grid.SetRow(_ele, 1);
            //Grid.SetRowSpan(_ele, 1);
            //Grid.SetColumnSpan(_ele, 1);
            ItemGrid.Children.Add(_ele);

            ///
            /// settings only visible if we can use them. There must be a binding way to do this, but not sure
            /// 

            if (!_item.HasSettingsUI)
            {
                ActivateSettings.Visibility = Visibility.Hidden;
            }

            ///
            /// Capture the id
            /// 

            _item.ConfigurationChanged += new RootfusSurfaceItemWrapper.ConfigurationChangedCallback(_item_ConfigurationChanged);
        }

        /// <summary>
        /// Deal with an item changing - just forward it!
        /// </summary>
        void _item_ConfigurationChanged()
        {
            OnItemChanged();
        }

        public RootfusSurfaceItemWrapper Wrapper { get { return _item; } }

        /// <summary>
        /// Exposes the list of output pins that our item has!
        /// </summary>
        public IList<string> OutputPins
        {
            get { return _item.OutputPinNames; }
        }

        /// <summary>
        /// Internal list of external connections.
        /// </summary>
        private List<SurfaceItemConnector> _connections = new List<SurfaceItemConnector>();

        /// <summary>
        /// When a new connection is formed, this is called. This maintains our list of
        /// external item connections.
        /// </summary>
        /// <param name="con"></param>
        internal void AddIncomingConnection(SurfaceItemConnector con)
        {
            _connections.Add(con);
        }

        /// <summary>
        /// Remove an existing connection from our list. We need source as an explicit argument
        /// because the connection may have lost its source property by now, and that is how we
        /// remember who is doing what, not by connection object.
        /// </summary>
        /// <param name="con"></param>
        internal void RemoveIncomingConnection(SurfaceItemConnector con, SurfaceItem source)
        {
            _connections.Remove(con);
            _item.RemoveIncommingConnections(source.Wrapper);
        }

        /// <summary>
        /// The guy has clicked in the input pins. Show the dialog that lets them configure our inputs.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void rectInputPins_MouseDown(object sender, MouseButtonEventArgs e)
        {
            ShowConfigureInputDialog();
        }

        /// <summary>
        /// Put up the dialog box that allows them to configure the input pins.
        /// </summary>
        public void ShowConfigureInputDialog()
        {

            /// Add up everyone that can possibly feed this guy
            List<RootfusSurfaceItemWrapper> surface_items = new List<RootfusSurfaceItemWrapper>();
            foreach (var s in _connections)
            {
                surface_items.Add(s.Source.Wrapper);
            }

            /// TODO: Make sure that we don't display this guy twice!
            HookUpPinsDialog hp = new HookUpPinsDialog(surface_items, _item.InputPinConfiguration);

            ///
            /// Now show it!
            /// Because pin changes can cause changes to the item, we had better update it!
            /// 

            hp.Unloaded += delegate
            {
                _item.Update();
            };
            ShowDialogControl(hp);
        }

        /// <summary>
        /// Show another control on top of the current guy. This will hid the
        /// underlying adding UI -- due to problems in the way that works, that
        /// is always on top -- so we have to hid it!
        /// </summary>
        /// <param name="hp"></param>
        private void ShowDialogControl(FrameworkElement hp)
        {
            Canvas.SetTop(hp, Canvas.GetTop(this));
            Canvas.SetLeft(hp, Canvas.GetLeft(this));
            ///
            /// Unfortunately, the way the UI clipping works for AddIn's means that the addin's are
            /// always visible. Nothing can go on top -- so we are going to make things invisible for
            /// now. :( One day we'll get a graphic picture or something.
            /// See http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=788263&SiteID=1 for how to do this.
            ///
            _ele.Visibility = Visibility.Hidden;
            hp.Unloaded += delegate(object sender, RoutedEventArgs e)
            {
                _ele.Visibility = Visibility.Visible;
                _ele.InvalidateVisual();
            };

            ///
            /// Ok -- flash it up there!
            /// 

            Canvas parent = Parent as Canvas;
            parent.Children.Add(hp);
        }

        /// <summary>
        /// Callback when something about the item has changed.
        /// </summary>
        public delegate void ItemChangedCallback();

        /// <summary>
        /// Fires when the item has been modified and needs to be re-saved.
        /// </summary>
        public event ItemChangedCallback ItemChanged;

        /// <summary>
        /// Override to catpure locally.
        /// </summary>
        protected virtual void OnItemChanged()
        {
            if (ItemChanged != null)
            {
                ItemChanged();
            }
        }

        /// <summary>
        /// When the control is done, we need to load up the resize stuff. :-)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ControlNowLoaded(object sender, RoutedEventArgs e)
        {
            ///
            /// Resize is gone if we don't allow resize!
            /// 

            if (_item.ResizeAllowed)
            {
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(ItemGrid);
                adornerLayer.Add(new SurfaceItemResizingAdorner(ItemGrid));
            }
        }

        /// <summary>
        /// Our size has been changed. Normally this would not be required. However,
        /// the add-in mechanism doesn't seem to forward the size-changed events back
        /// to the add-in - so it never gets the message. Even though snoop indicates
        /// that it knows about the size change. :-)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SizeChangedFired(object sender, SizeChangedEventArgs e)
        {
            if (!Double.IsNaN(ItemGrid.Width) && !Double.IsNaN(ItemGrid.Height))
            {
                ///
                /// Calc the new size of the add-in
                /// 

                Size nsize = ItemSize;

                _item.RequestResize(nsize.Width, nsize.Height);
            }
        }

        /// <summary>
        /// The user wants to get rid of us! Kill ourselves off and any incoming and
        /// outgoing connections!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RemoveSurfaceItem_Click(object sender, RoutedEventArgs e)
        {
            ///
            /// Make sure everyone up there knows that we are on our way out!
            ///

            OnItemClosing();

            ///
            /// Get rid of any incoming connections. Setting all of these things to null will
            /// hve the side-effect of removing them from our connection - which is why we use
            /// the funny loop below.
            /// 

            while (_connections.Count > 0)
            {
                SurfaceItemConnector c = _connections[0];
                c.Host = null;
                c.Source = null;
                c.Sink = null;
            }

            ///
            /// Finally, remove us from the drawing surface and release anything we are holding on to.
            /// 

            ItemGrid.Children.Remove(_ele);
            _item.ConfigurationChanged -= new RootfusSurfaceItemWrapper.ConfigurationChangedCallback(_item_ConfigurationChanged);
        }

        /// <summary>
        /// Callback delegate for when this item is on its way out.
        /// </summary>
        /// <param name="item"></param>
        public delegate void ItemClosingCallback(SurfaceItem item);

        /// <summary>
        /// Fires when this item is going away.
        /// </summary>
        public event ItemClosingCallback ItemClosing;

        /// <summary>
        /// Let anyone know who is listening that we are going away!
        /// </summary>
        private void OnItemClosing()
        {
            if (ItemClosing != null)
            {
                ItemClosing(this);
            }
        }

        /// <summary>
        /// The item is just going away. We need to make sure we aren't
        /// using it in any of the connections. If we are, kill that connection.
        /// </summary>
        /// <param name="item"></param>
        internal void RemoveReferencesTo(SurfaceItem item)
        {
            ///
            /// Find all connctions that deal with this item and null them out - which
            /// should have the effect of removing all connections.
            /// 

            var used_in_connections = from c in _connections
                                      where c.Source == item || c.Sink == item
                                      select c;

            foreach (var c in used_in_connections.ToArray())
            {
                c.Source = null;
                c.Sink = null;
                _connections.Remove(c);
            }
        }

        /// <summary>
        /// Gets/Sets the size of the item that we are showing. The set doesn't work if we aren't allowed
        /// to resize items.
        /// </summary>
        public Size ItemSize
        {
            get
            {
                double width = ItemGrid.ColumnDefinitions[1].ActualWidth;
                double height = ItemGrid.RowDefinitions[1].ActualHeight;
                return new Size(width, height);
            }
            set
            {
                if (_item.ResizeAllowed)
                {
                    double total_width = ItemGrid.ColumnDefinitions[0].Width.Value
                        + ItemGrid.ColumnDefinitions[2].Width.Value
                        + value.Width;
                    double total_height =
                        ItemGrid.RowDefinitions[0].Height.Value
                        + ItemGrid.RowDefinitions[2].Height.Value
                        + value.Height;

                    ItemGrid.Width = total_width;
                    ItemGrid.Height = total_height;
                }
            }
        }

        private void ActivateSettings_Click(object sender, RoutedEventArgs e)
        {
            ShowDialogControl(new SISettingsUI(_item.ConfigUI, _item));

        }
    }
}
